// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gsutil

import (
	"bytes"
	"io/ioutil"
	"os"
	"path/filepath"

	"go.chromium.org/luci/common/errors"
)

// Boto represents a subset of .boto gsutil configuration file.
type Boto struct {
	StateDir          string // value of GSUtil.state_dir
	RefreshToken      string // value of Credentials.gs_oauth2_refresh_token
	GCEServiceAccount string // value of GoogleCompute.service_account
	ProviderLabel     string // value of OAuth2.provider_label
	ProviderAuthURI   string // value of OAuth2.provider_authorization_uri
	ProviderTokenURI  string // value of OAuth2.provider_token_uri
}

// Write creates the config file.
func (b *Boto) Write(path string) error {
	buf := bytes.Buffer{}

	line := func(s string) {
		buf.WriteString(s)
		buf.WriteRune('\n')
	}

	opts := func(name, value string) {
		if value != "" {
			buf.WriteString(name)
			buf.WriteString(" = ")
			buf.WriteString(value)
			buf.WriteRune('\n')
		}
	}

	line("# Autogenerated by LUCI. Do not edit.")
	line("")
	line("[GSUtil]")
	opts("software_update_check_period", "0")
	opts("state_dir", b.StateDir)
	if b.RefreshToken != "" {
		line("")
		line("[Credentials]")
		opts("gs_oauth2_refresh_token", b.RefreshToken)
	}
	if b.GCEServiceAccount != "" {
		line("")
		line("[GoogleCompute]")
		opts("service_account", b.GCEServiceAccount)
	}
	if b.ProviderLabel != "" || b.ProviderAuthURI != "" || b.ProviderTokenURI != "" {
		line("")
		line("[OAuth2]")
		opts("provider_label", b.ProviderLabel)
		opts("provider_authorization_uri", b.ProviderAuthURI)
		opts("provider_token_uri", b.ProviderTokenURI)
	}

	return ioutil.WriteFile(path, buf.Bytes(), 0600)
}

// PrepareStateDir prepares a directory (based on b.StateDir) for gsutil to keep
// its state and drops .boto config there.
//
// Returns path to the created .boto file.
func PrepareStateDir(b *Boto) (string, error) {
	if err := os.MkdirAll(b.StateDir, 0700); err != nil {
		return "", errors.Annotate(err, "failed to create gsutil state dir at %s", b.StateDir).Err()
	}

	botoCfg := filepath.Join(b.StateDir, ".boto")
	if err := b.Write(botoCfg); err != nil {
		return "", errors.Annotate(err, "failed to write %s", botoCfg).Err()
	}

	// Make sure the credentials cache file is empty, otherwise it will grow
	// after each server launch, since it uses refresh_token (which we may
	// generate randomly) as a cache key. We don't really need this cache anyway.
	if err := os.Remove(filepath.Join(b.StateDir, "credstore")); err != nil && !os.IsNotExist(err) {
		return "", errors.Annotate(err, "failed to remove credstore").Err()
	}

	return botoCfg, nil
}
